//
// Created by Administrator on 2021/5/4.
//

/*
在一个小城市里，有m个房子排成一排，你需要给每个房子涂上 n种颜色之一（颜色编号为 1 到 n）。
 有的房子去年夏天已经涂过颜色了，所以这些房子不需要被重新涂色。

我们将连续相同颜色尽可能多的房子称为一个街区。（比方说 houses = [1,2,2,3,3,2,1,1] ，
 它包含 5 个街区 [{1}, {2,2}, {3,3}, {2}, {1,1}] 。）

给你一个数组houses，一个m * n的矩阵cost和一个整数target，其中：

houses[i]：是第i个房子的颜色，0表示这个房子还没有被涂色。
cost[i][j]：是将第i个房子涂成颜色j+1的花费。
请你返回房子涂色方案的最小总花费，使得每个房子都被涂色后，恰好组成target个街区。如果没有可用的涂色方案，请返回-1。


示例 1：
输入：houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出：9
解释：房子涂色方案为 [1,2,2,1,1]
此方案包含 target = 3 个街区，分别是 [{1}, {2,2}, {1,1}]。
涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9。

示例 2：
输入：houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出：11
解释：有的房子已经被涂色了，在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3 个街区，分别是 [{2,2}, {1}, {2,2}]。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11。

示例 3：
输入：houses = [0,0,0,0,0], cost = [[1,10],[10,1],[1,10],[10,1],[1,10]], m = 5, n = 2, target = 5
输出：5

示例 4：
输入：houses = [3,1,2,3], cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]], m = 4, n = 3, target = 3
输出：-1
解释：房子已经被涂色并组成了 4 个街区，分别是 [{3},{1},{2},{3}] ，无法形成 target = 3 个街区。

m == houses.length == cost.length
n == cost[i].length
1 <= m <= 100
1 <= n <= 20
1 <= target<= m
0 <= houses[i]<= n
1 <= cost[i][j] <= 10^4


来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/paint-house-iii
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。*/
#include <vector>
#include <iostream>
#include <climits>
#include <tuple>
using namespace std;

class Solution {
    /** 提示
     * Define dp[i][j][k] as the minimum cost where we have k neighborhoods in the first i houses
     * and the i-th house is painted with the color j.
    我们可以使用动态规划解决本题。
    设 dp(i,j,k) 表示将 [0, i]的房子都涂上颜色，最末尾的第 i 个房子的颜色为 j，并且它属于第 k 个街区时，需要的最少花费。
    在进行状态转移时，我们需要考虑「第 i−1 个房子的颜色」，这关系到「花费」以及「街区数量」的计算，因此我们还需要对其进行枚举。
    设第 i−1 个房子的颜色为 j0 ，我们可以分类讨论出不同情况下的状态转移方程
    if houses[i] != -1 :  // 说明第 i 个房子已经涂过颜色了，必定有house[i] = j
        if houses[i] != j  dp[i][j]k] = MAX
        else    // case A
            if j == j0 //第 i−1 个房子和第 i 个房子属于同一个街区
                dp[i][j][k] = dp[i-1][j][k]
            else // 和上一个房子属于不同街区
                dp[i][j][k] = min(dp[i-1][j0][k-1])
     else // 需要涂色 花费cost[i][j]  case B
        if j == j0 // 和上一个房子同属于一个街区
            dp[i][j][k] = dp[i-1][j][k] + cost[i][j]
        else
            dp[i][j][k] = min(dp[i-1][j0][k-1]) + cost[i][j]
     case A 、 case B 的转移方程十分相像，可以合并处理
     边界条件：
        dp[0][j][0]
        = MAX if houses[i] != -1 and houses[i] != j
        = 0 if house[i] != -1 and houses[i] == j
        = cost[i][j] if houses[i] == -1
        if i == 0 and k != 0 dp[0][j][k] = MAX
     */
private:
    // 极大值
    // 选择 INT_MAX / 2 的原因是防止整数相加溢出
    static constexpr int INFTY = INT_MAX / 2;

public:
    int minCost(vector<int> &houses, vector<vector<int>> &cost, int m, int n, int target) {
        // 将颜色调整为从 0 开始编号，没有被涂色标记为 -1
        for (int &c: houses) {
            --c;
        }

        // dp 所有元素初始化为极大值
        vector<vector<vector<int>>> dp(m, vector<vector<int>>(n, vector<int>(target, INFTY)));

        for (int i = 0; i < m; ++i) {  // 遍历房子
            for (int j = 0; j < n; ++j) {  // 遍历颜色
                if (houses[i] != -1 && houses[i] != j) { // 已经涂色 并且 其颜色和遍历到的颜色不符，则直接跳过（默认是极大）
                    continue;
                }

                for (int k = 0; k < target; ++k) { // 枚举街区
                    for (int j0 = 0; j0 < n; ++j0) { // 上一个房子的颜色
                        if (j == j0) { // 和上一个房子的颜色相同，属于同一个街区
                            if (i == 0) {
                                if (k == 0) dp[i][j][k] = 0;
                            } else dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k]);
                        } else if (i > 0 && k > 0) // 属于不同街区
                            dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j0][k - 1]);
                    }
                    // 如果是需要涂色的
                    if (dp[i][j][k] != INFTY && houses[i] == -1) {
                        dp[i][j][k] += cost[i][j];
                    }
                }
            }
        }

        int ans = INFTY;
        for (int j = 0; j < n; ++j) { // 找最小的答案
            ans = min(ans, dp[m - 1][j][target - 1]);
        }
        return ans == INFTY ? -1 : ans;
    }
};
class Solution2 { // 优化 看不懂
private:
    // 极大值
    // 选择 INT_MAX / 2 的原因是防止整数相加溢出
    static constexpr int INFTY = INT_MAX / 2;

    using TIII = tuple<int, int, int>;

public:
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        // 将颜色调整为从 0 开始编号，没有被涂色标记为 -1
        for (int& c: houses) {
            --c;
        }

        // dp 所有元素初始化为极大值
        vector<vector<vector<int>>> dp(m, vector<vector<int>>(n, vector<int>(target, INFTY)));
        vector<vector<TIII>> best(m, vector<TIII>(target, {INFTY, -1, INFTY}));

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (houses[i] != -1 && houses[i] != j) {
                    continue;
                }

                for (int k = 0; k < target; ++k) {
                    if (i == 0) {
                        if (k == 0) {
                            dp[i][j][k] = 0;
                        }
                    }
                    else {
                        dp[i][j][k] = dp[i - 1][j][k];
                        if (k > 0) {
                            // 使用 best(i-1,k-1) 直接得到 dp(i,j,k) 的值
                            auto&& [first, first_idx, second] = best[i - 1][k - 1];
                            dp[i][j][k] = min(dp[i][j][k], (j == first_idx ? second : first));
                        }
                    }

                    if (dp[i][j][k] != INFTY && houses[i] == -1) {
                        dp[i][j][k] += cost[i][j];
                    }

                    // 用 dp(i,j,k) 更新 best(i,k)
                    auto&& [first, first_idx, second] = best[i][k];
                    if (dp[i][j][k] < first) {
                        second = first;
                        first = dp[i][j][k];
                        first_idx = j;
                    }
                    else if (dp[i][j][k] < second) {
                        second = dp[i][j][k];
                    }
                }
            }
        }

        int ans = INFTY;
        for (int j = 0; j < n; ++j) {
            ans = min(ans, dp[m - 1][j][target - 1]);
        }
        return ans == INFTY ? -1 : ans;
    }
};

int main() {
    vector<int> houses{0, 0, 0, 0, 0};
    vector<vector<int>> cost{{1,  10},
                             {10, 1},
                             {10, 1},
                             {1,  10},
                             {5,  1}};
    Solution2 sol;
    cout << sol.minCost(houses, cost, 5, 2, 3) << endl;
    return 0;
}